package cn.jhz.learn.blog.component.shiro.filter;


import cn.jhz.learn.blog.component.GLOBAL;
import cn.jhz.learn.blog.component.shiro.JwtToken;
import cn.jhz.learn.blog.component.shiro.exception.NullCredentialsException;
import cn.jhz.learn.blog.util.JwtUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.jose4j.jwt.MalformedClaimException;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
 * 要求请求用户通过身份验证才能继续请求,否则要求用户通过JWT的校验进行登录.成功登录后,它们可以继续访问请求的资源/url.
 * <p/>
 * 基本认证功能如下:
 * <ol>
 *     <li>对于需要身份验证的资源的请求</li>
 *     <li>服务器以401响应状态，设置WWW-Authenticate头，以及通知用户请求的资源需要身份验证.</li>
 *     <li>客户端使用账号和密码登录获取JWT令牌</li>
 *     <li>客户端重新发送请求与报头:<br/>
 *       <p><code>Authorization: <em>Base64url-encoded_Header.Payload.Signature</em></code></p>
 *     </li>
 *     <li>
 *         对客户端请求报头<code><em>X-Authorization-With:JWT</em></code>进行校验
 *     </li>
 * </ol>
 * <em>默认所有发出请求的主体没有经过身份验证,每次请求都会调用onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse)方法</em>
 * @author machine_005
 * @version 1.0
 */
public class JwtAuthenticationFilter extends BasicHttpAuthenticationFilter {
    /**
     * This class's private logger.
     */
    private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationFilter.class);

    /**
     * 过滤器拦截请求的入口方法,将对请求进行校验并尝试登录.
     * 返回 true则允许访问,返回false则禁止访问,会进入<code><em>onAccessDenied()</em></code>.
     * 此方法默认禁止访问其父类定义的登录请求,所有登录请求将被此过滤器禁止.
     * <P/>
     * @param request 当前HTTP servlet请求.
     * @param response 当前HTTP servlet响应.
     * @param mappedValue
     * @return <code>true</code> 如果请求应该被允许访问
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        /*TODO:用来判断是否是登录请求,不会拦截登录请求,用来检测Header中是否包含 JWT token 字段*/
        if (isLoginRequest(request, response))
            return false;
        boolean allowed = false;
        try {
            /*检测Header里的 JWT token是否正确,尝试使用 token进行登录*/
            allowed = executeLogin(request, response);
        } catch (IllegalStateException e) { // not found any token
            log.error("Not found any token");
        } catch (Exception e) {
            log.error("Error occurs when login", e);
        }
        return allowed || super.isPermissive(mappedValue);
    }

    /**
     * 从http报头中提供的凭据创建用于登录尝试的AuthenticationToken(JwtToken).
     * <p/>
     * 实现:
     * <ol>
     *     <li>将报头中X-Authorization-With的token取出</li>
     *     <li>解析JWT中的aud</li>
     *     <li>返回封装token和aud的JwtToken</li>
     * </ol>
     * @param servletRequest 请求
     * @param servletResponse 响应
     * @return 用于执行登录尝试的AuthenticationToken
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String authorization = httpServletRequest.getHeader(GLOBAL.TOKEN_INFO.AUTH_HEADER);

        if(!StringUtils.isBlank(authorization))
            try {
                return  new JwtToken(authorization, JwtUtil.getInstance().parseJWT(authorization).getAudience());
            } catch (MalformedClaimException | InvalidJwtException e) {
                log.error(e.getMessage());
                e.printStackTrace();
            }
        return null;
    }

    /**
     * 将JwtToken进行登录并验证
     * @param request 请求
     * @param response 响应
     * @return <code>true</code> 登录校验成功
     * @throws Exception <ul>
     *                      <li>必须创建一个有效的非空<code><em>AuthenticationToken</code></em>以执行登录尝试.
     *                          如果创建的JwtToken为null,将会抛出<code><em>IllegalStateException</code></em>.</li>
     *                      <li>登陆尝试失败将会抛出<code><em>AuthenticationException</code></em>.</li>
     *                   </ul>
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        AuthenticationToken token = createToken(request, response);
        if (token == null) {
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken "
                    + "must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        }
        try {
            Subject subject = getSubject(request, response);
            subject.login(token);
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            return onLoginFailure(token, e, request, response);
        }
    }

    /**
     * 在登陆成功后进入此方法, 对Authorization令牌的过期时间进行更新
     * @param token 令牌
     * @param subject 用户主体
     * @param request 请求
     * @param response 响应
     * @return 默认返回true
     * @throws Exception 令牌更新失败将抛出异常
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
                                     ServletResponse response) throws Exception {
        HttpServletResponse httpResponse = WebUtils.toHttp(response);
        String newToken = null;
        if (token instanceof JwtToken) {
            newToken = JwtUtil.getInstance().reflashJwsToken(token.getCredentials().toString());
        }
        if (newToken != null) {
            httpResponse.setHeader(GLOBAL.TOKEN_INFO.AUTH_HEADER, newToken);
            httpResponse.setHeader(GLOBAL.HEADER_INFO.ACCESS_CONTROL_EXPOSE_HEADERS, GLOBAL.TOKEN_INFO.AUTH_HEADER);
        }
        return true;
    }

    /**
     * 检测Header中是否包含X-Authorization-With的token令牌,如果不存在返回true,借助executeLogin方法抛出异常.
     * 如果令牌存在返回false,说明令牌在之前的校验中被证实无效.
     * @param request 请求
     * @param response 响应
     * @return true 报头令牌不存在
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        return ((HttpServletRequest) request).getHeader(GLOBAL.TOKEN_INFO.AUTH_HEADER) == null;
    }

    /**
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        HttpServletResponse httpResponse = WebUtils.toHttp(response);
        httpResponse.setCharacterEncoding("UTF-8");
        httpResponse.setContentType("application/json;application/x-www-form-urlencoded;charset=UTF-8");
        httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
        PrintWriter writer = httpResponse.getWriter();
        writer.write("{\"errCode\": 401, \"msg\": \"UNAUTHORIZED\"}");
        fillCorsHeader(WebUtils.toHttp(request), httpResponse);
        return false;
    }

    /**
     *
     * @param httpServletRequest
     * @param httpServletResponse
     */
    protected void fillCorsHeader(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
        httpServletResponse.setHeader(GLOBAL.HEADER_INFO.ACCESS_CONTROL_ALLOW_ORIGIN, httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader(GLOBAL.HEADER_INFO.ACCESS_CONTROL_ALLOW_METHODS, "GET,POST,PUT,PATCH,DELETE,OPTIONS,HEAD");
        httpServletResponse.setHeader(GLOBAL.HEADER_INFO.ACCESS_CONTROL_ALLOW_HEADERS, httpServletRequest.getHeader("Access-Control-Request-Headers"));
        httpServletResponse.setHeader(GLOBAL.HEADER_INFO.ACCESS_CONTROL_EXPOSE_HEADERS, GLOBAL.TOKEN_INFO.AUTH_HEADER);
    }
    /**
     * 前置处理
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        /*跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态*/
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
    /**
     * 后置处理
     */
    @Override
    protected void postHandle(ServletRequest request, ServletResponse response) {
        /*添加跨域支持*/
        fillCorsHeader(WebUtils.toHttp(request), WebUtils.toHttp(response));
    }

}











//    /**
//     * 添加跨域支持
//     */
//    protected void fillCorsHeader(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
//        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
//        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,HEAD");
//        httpServletResponse.setHeader("Access-Control-Allow-Headers",
//                httpServletRequest.getHeader("Access-Control-Request-Headers"));
//    }